fix(operations): replace the component removal recipe that undoes itself - #684
Aleksei Sviridkin (lexfrei) wants to merge 4 commits into
Conversation
✅ Deploy Preview for cozystack ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
0125811 to
2e750d6
Compare
IvanHunters
left a comment
There was a problem hiding this comment.
Verdict
NOT LGTM
Reviewed at 2e750d69f against merge-base 257bbbbe3.
The fix itself is sound. I re-derived every factual claim in the PR body against the platform source at the matching tags rather than taking them on trust: Package ownership of the HelmRelease, Flux's default deletion behaviour, metallb's CRDs carrying no resource-policy, cert-manager's contrasting crds.keep, and the exact patch releases where the keep annotation appears. All of it holds. What blocks is that v1.0 still documents the recipe being replaced everywhere else, with no warning on the page.
Findings
- [MINOR]
content/en/docs/next/operations/configuration/components.md:83, Warning's named CRD list undercounts the blast radius under metallb.frrk8s.enabled: true - [MINOR]
content/en/docs/next/operations/configuration/components.md:89, No signal for when the destructive uninstall has actually finished - [MAJOR]
content/en/docs/v1.0/operations/configuration/components.md:70, the recipe this PR replaces is still live on v1.0, with no warning
Claim mismatches
[UNVERIFIABLE] "hugo --gc --minify builds clean". I could not reproduce it here: the build hung on Hugo Modules resolution, with the docsy module never appearing in GOMODCACHE despite proxy.golang.org being reachable, and was killed after two attempts. The sandbox Hugo is v0.153.2, below the 0.164.0 extended that CONTRIBUTING.md requires, which may be why. Secondary evidence supports the claim without settling it: the {{% alert %}} shortcode form used in the diff appears identically in 392 other places on the site, and open and close tags balance 1:1 in all seven touched files. A gap on my side, not a defect.
Caveats
v0/operations/configuration/components.mdalso still carrieskubectl delete hr, and that one is correctly out of scope.v0predates the Package mechanism, so the removal path there is different, and CONTRIBUTING.md explicitly demotes it to legacy-only fixes.v1.0has neither property.hack/check-i18n.shfails today on the four stale translations the PR body names (content/{de,hi,ru,zh-cn}/_index.md). None are touched here, so it is pre-existing and matches the body's own disclosure.
Recommended follow-ups
- File the tracking issue for the
v1.0gap if it does not get folded into this PR. - Consider replacing the hand-enumerated kind list in the Warning with a pointer to whatever CRDs the component's chart bundles, so a component like metallb with frr-k8s does not need the list maintained per component.
Findings not anchored to changed lines
These reference code outside this PR's diff (unchanged files, or lines outside a hunk), so GitHub cannot render them inline.
[MAJOR] content/en/docs/v1.0/operations/configuration/components.md:70 the recipe this PR replaces is still live on v1.0, with no warning
v1.0/operations/configuration/components.md:70 still tells the reader to run kubectl delete hr -n <namespace> <component>, and that page carries no warning at all. v1.0/install/providers/hetzner.md:347 still has the bare - metallb under disabledPackages that this PR fixes on the v1.1 equivalent, and the platform matches disabledPackages against the fully-qualified cozystack.metallb, so the bare name does nothing.
On v1.0 the rendered Package never carried helm.sh/resource-policy: keep, which is the same reason the PR gives for the v1.1/v1.2 caveat. So the v1.0 page documents the destructive path with zero disclosure:
$ grep -n 'kubectl delete hr' content/en/docs/v1.0/operations/configuration/components.md
70:kubectl delete hr -n <namespace> <component>
$ grep -c 'alert title="Warning"' content/en/docs/v1.0/operations/configuration/components.md
0
$ grep -n -A1 'disabledPackages' content/en/docs/v1.0/install/providers/hetzner.md | sed -n '3,4p'
346: disabledPackages:
347- - metallb
$ for t in v1.0.8 v1.1.4 v1.1.5; do printf '%-8s ' $t; \
git show $t:packages/core/platform/templates/_helpers.tpl | grep -c resource-policy; done
v1.0.8 0
v1.1.4 0
v1.1.5 2
v1.0 is not demoted. hugo.yaml carries exactly one hidden: true and it belongs to next, not to v1.0; CONTRIBUTING.md demotes only v0 ("Treat v0/ as legacy"), and its own routing table says a fix that also applies to an older supported version gets copied into each relevant vX.Y/.
The PR body scopes this out on purpose and explains why (v1.0 needs a different correction rather than a copy, since disabledPackages alone still prunes the Package there). That reasoning is sound, and I verified it. What is missing is the follow-up: no tracking issue, no companion change, and no notice on the page. Either extend this PR to v1.0 with the version-scoped variant of the same two paragraphs, or file the issue before merge.
| Then delete the Package object. `kubectl get packages` lists the names. | ||
|
|
||
| {{% alert title="Warning" color="warning" %}} | ||
| Deleting the Package uninstalls the component's Helm release, and that destroys more than the workloads. Anything the chart rendered as an ordinary template without `helm.sh/resource-policy: keep` goes with the release, CRDs and namespaces included, and Kubernetes deletes every custom resource of those CRD kinds along with them. Removing `cozystack.metallb` takes the MetalLB CRDs and with them every IPAddressPool, L2Advertisement, BGPPeer and the rest of those kinds cluster-wide; removing `cozystack.cozystack-basics` takes the `cozy-public` namespace and everything stored in it. Back up anything you still need first. |
There was a problem hiding this comment.
[MINOR] Warning's named CRD list undercounts the blast radius under metallb.frrk8s.enabled: true
The alert names IPAddressPool, L2Advertisement, BGPPeer as the destroyed kinds. packages/system/metallb/charts/metallb/charts/frr-k8s/charts/crds/templates/*.yaml renders four more CRDs (frrconfigurations, frrnodestates, bgpsessionstates, frrk8sconfigurations) under a different group, frrk8s.metallb.io, as a nested subchart of the same cozystack.metallb release, and metallb.frrk8s.enabled: true is a documented toggle. The preceding generic sentence already covers this correctly, so this is not wrong, just incomplete for BGP-mode operators reading only the named list. Same text is duplicated identically at v1.1:78, v1.2:83, v1.3:83, v1.4:83, v1.5:83, v1.6:83.
There was a problem hiding this comment.
Dropped the kind list. The sentence now points at the chart and says subcharts count, so it does not need updating per component.
One scope correction: frr-k8s only landed in packages/system/metallb at v1.5.0, so v1.1 through v1.4 never shipped those four CRDs. The new wording holds on all of them anyway.
| The namespace a component installs into is the exception: the operator applies that one itself, outside the component's release and with no ownerReference, so the uninstall never had it to remove. | ||
|
|
||
| ```bash | ||
| kubectl delete package.cozystack.io <package-name> |
There was a problem hiding this comment.
[MINOR] No signal for when the destructive uninstall has actually finished
internal/operator/package_reconciler.go registers no finalizer on Package (verified: the only finalizer usage in the tree belongs to an unrelated tapmaterializer reconciler), so kubectl delete package.cozystack.io returns as soon as the Package object itself is gone. The owned HelmRelease's deletion, and the helm uninstall inside it that destroys the CRDs/namespaces the Warning describes, proceeds asynchronously afterward via the ownerReference cascade. The recipe has no follow-up check for confirming the destructive step is actually done. Same text is duplicated identically at v1.1:84, v1.2:89, v1.3:89, v1.4:89, v1.5:89, v1.6:89.
There was a problem hiding this comment.
Added a wait after the delete:
kubectl wait --for=delete helmrelease/<component> --namespace <namespace> --timeout=10mwith a line saying why the delete returns early.
|
IvanHunters folded v1.0 into this PR instead of filing the issue. It needed different text, not a copy. At i18n-lint was red on four stale home-page translations, nothing to do with this PR. Fixed in |
4bdf52e to
35a2827
Compare
|
IvanHunters I rebased onto main, so the SHAs in my earlier replies are gone. The i18n fix I referenced as Two changes since you looked. On v1.1 and v1.2 the note about v1.1.0 to v1.1.4 and v1.2.0 sat in the middle of the two-step instructions and read as if one of the steps applied to the old patches, so it's now a separate paragraph ahead of the steps. On v1.0 to v1.4 the warning now says that removing |
IvanHunters
left a comment
There was a problem hiding this comment.
Verdict
NOT LGTM
The per-version research behind this is genuinely good, and all three findings from my last round are closed: the resource-policy: keep boundaries are right at every tag, the prefixed cozystack.* names fix a real no-op on v1.0/v1.1, and the new warning covers the MetalLB subchart CRDs. What blocks it is the new procedure's end: the completion check cannot fail, the sentence the recipe rests on is conditional, the Warning's survival rule is contradicted by the very object this PR removed from it, and v0 keeps the old command with no warning at all.
Findings
- [MAJOR]
content/en/docs/next/operations/configuration/components.md:95, the completion check cannot fail on a wrong guess, and its two placeholders are underivable
- [MAJOR]
content/en/docs/next/operations/configuration/components.md:100, "Flux uninstalls the release when the HelmRelease goes away" is conditional, and Cozystack reaches the other branch itself
- [MAJOR]
content/en/docs/next/operations/configuration/components.md:83,resource-policy: keepis not the whole survival rule from v1.5.0, and this PR's own edit is the counterexample
- [MAJOR]
content/en/docs/v1.0/operations/configuration/components.md:70, the v0 tree keeps the bare command and gets none of the warning
- [MINOR]
content/en/docs/v1.1/operations/configuration/components.md:44,kubectl get packagecannot list a name that is not enabled yet
Caveats
- On the v1.0, v1.1 and v1.2 trees the destructive step is the values edit itself, and those pages get no completion signal at all while the newer ones now do. The same
kubectl waitwould work there, subject to the first finding. - Two statements in the PR body do not hold on this head.
hack/check-i18n.sh checkexits 0 here, so i18n-lint is not red. And a condition wait on the platform HelmRelease is not useless:--for=condition=has been generation-gated since kubectl v1.21 and Flux stampsobservedGenerationon the condition, sokubectl wait hr/cozystack-platform -n cozy-system --for=condition=Ready=trueis a real gate after the jsonpath check. - I checked whether the guardrail's
Capabilitiesgate opens a hole on clusters below Kubernetes 1.30, where the policy is skipped entirely andtenant-rootwould go with the release after all. It does not reach a supported install: the v1.5.0 changelog requires Kubernetes 1.33+ on the management cluster from that release on.
| Nothing holds a finalizer on the Package, so this command returns as soon as the object is gone and the uninstall it triggers runs afterwards. Wait on the HelmRelease to know the destructive part has finished: | ||
|
|
||
| ```bash | ||
| kubectl wait --for=delete helmrelease/<component> --namespace <namespace> --timeout=10m |
There was a problem hiding this comment.
[MAJOR] the completion check cannot fail on a wrong guess, and its two placeholders are underivable
kubectl wait --for=delete suppresses not-found unconditionally, so a name the reader guessed wrong exits 0 and prints nothing, which is indistinguishable from the object having been removed. In kubectl v1.34.1:
// pkg/cmd/wait/wait.go:370-372, 379-380
isForDelete := strings.ToLower(o.ForCondition) == "delete"
if visitor, ok := visitor.(*resource.Result); ok && isForDelete {
visitor.IgnoreErrors(apierrors.IsNotFound)
}
...
if visitCount == 0 && !isForDelete {
return errNoMatchingResourcesThere is no --ignore-not-found to turn that off, and the asymmetry is the trap: a real object prints condition met, a wrong one prints nothing and still exits 0.
The reader's only handle is the Package name from kubectl get packages, e.g. cozystack.metallb. Neither placeholder follows from it. The HelmRelease name and namespace come from the PackageSource install block, which this page never mentions:
$ git show origin/main:packages/core/platform/sources/metallb.yaml | sed -n '10,25p'
- name: metallb
path: system/metallb
install:
privileged: true
namespace: cozy-metallb
releaseName: metallbThere is also no naming rule to fall back on, because one Package can render several releases. And the namespace a reader is most likely to substitute is the cozy-system printed on this page's own Package example.
The operator already labels every HelmRelease it generates with the Package that owns it, at every tag from v1.0.8 to main:
$ git grep -n 'cozystack.io/package' origin/main -- internal/operator/package_reconciler.go
264: labels["cozystack.io/package"] = pkg.Name
$ for t in v1.0.8 v1.1.7 v1.3.6 v1.6.3; do git grep -c 'cozystack.io/package' $t -- internal/operator/package_reconciler.go; done
2
2
2
2So the step before the delete can produce both values and cover a multi-release Package at the same time:
kubectl get helmrelease --all-namespaces --selector cozystack.io/package=<package-name>There was a problem hiding this comment.
Fixed in f621e84. The step before the delete lists the releases the Package owns and prints namespace, name and spec.suspend as columns, so both values come out of the listing and a Package that renders several releases is covered.
kubectl get helmrelease --all-namespaces --selector cozystack.io/package=<package-name> \
--output custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,SUSPENDED:.spec.suspend'The line under the wait says --for=delete exits 0 for a name that was never there, so the values have to come from the listing rather than a guess. v1.0 gets the same listing, taken before the values edit, because there the Package goes away with the component.
|
|
||
| Deleting the Package while the platform values still render it means the next platform upgrade brings it back, undoing the removal one level up. Nothing reports this at the time: the delete succeeds either way and the Package reappears whenever that upgrade happens to run. | ||
|
|
||
| `kubectl delete hr` is not a lighter-weight version of this. Flux uninstalls the release when the HelmRelease goes away, so it destroys the same CRDs and custom resources, and then the Package recreates the HelmRelease and the chart reinstalls. The workloads come back, the custom resources do not. If you have run it before, those custom resources are already gone and have to be recreated from your own manifests or a backup. |
There was a problem hiding this comment.
[MAJOR] "Flux uninstalls the release when the HelmRelease goes away" is conditional, and Cozystack reaches the other branch itself
The paragraph states this flat, and the wait above rests on it. The pinned controller is ghcr.io/fluxcd/helm-controller:v1.5.0 (internal/fluxinstall/manifests/fluxcd.yaml:8097), and at that tag the uninstall is gated on suspend while the finalizer removal is not:
// helm-controller v1.5.0, internal/controller/helmrelease_controller.go
func (r *HelmReleaseReconciler) reconcileDelete(ctx context.Context, obj *v2.HelmRelease) (ctrl.Result, error) {
// Only uninstall the release and delete the HelmChart resource if the
// resource is not suspended.
if !obj.Spec.Suspend {
if err := r.reconcileReleaseDeletion(ctx, obj); err != nil {
return ctrl.Result{}, err
}
...
}
if !obj.DeletionTimestamp.IsZero() {
controllerutil.RemoveFinalizer(obj, v2.HelmReleaseFinalizer)Suspending is routine, and Cozystack gets there on its own rather than only by an operator running flux suspend:
$ git grep -n 'Spec.Suspend = true\|Spec.Suspend = existing' origin/main -- internal/
internal/controller/fluxplunger/flux_plunger.go:258: latestHR.Spec.Suspend = true
internal/operator/package_reconciler.go:454: hr.Spec.Suspend = existing.Spec.SuspendThe plunger sets it, and the operator carries it across every subsequent update. The two halves compound with the finding above: the admin deletes the Package, the HelmRelease disappears in milliseconds with nothing uninstalled, and the wait reports the destructive part finished while every workload, CRD and custom resource is still there. Nothing on the page lets the reader tell that apart from success. One sentence telling them to check spec.suspend first, and that a returned wait is not by itself proof, would close it.
There was a problem hiding this comment.
Fixed in f621e84. The same if !obj.Spec.Suspend is in helm-controller v1.4.3, which v1.0 through v1.4 run, so it applies to every page in this PR and not only the ones on v1.5.0. The listing before the delete prints SUSPENDED and the page says to clear it first; the wait paragraph now ends with "a returned wait says the HelmRelease is gone, not that the uninstall ran". The PR body carried the same flat claim and is rewritten.
| Then delete the Package object. `kubectl get packages` lists the names. | ||
|
|
||
| {{% alert title="Warning" color="warning" %}} | ||
| Deleting the Package uninstalls the component's Helm release, and that destroys more than the workloads. Anything the chart rendered as an ordinary template without `helm.sh/resource-policy: keep` goes with the release, CRDs and namespaces included, and Kubernetes deletes every custom resource of those CRD kinds along with them. Removing `cozystack.metallb` takes every CRD the MetalLB chart bundles, subcharts included, and with them every custom resource of those kinds cluster-wide; removing `cozystack.cozystack-basics` takes the `cozy-public` namespace and everything stored in it. Back up anything you still need first. |
There was a problem hiding this comment.
[MAJOR] resource-policy: keep is not the whole survival rule from v1.5.0, and this PR's own edit is the counterexample
The Warning gives the annotation as the only thing that keeps an object alive through the uninstall. Since v1.5.0 there is a second, independent mechanism, and the rule as stated predicts the opposite outcome for the object this PR just removed from that sentence.
$ git show origin/main:packages/core/platform/templates/deletion-protection.yaml
ValidatingAdmissionPolicy that blocks DELETE on any object carrying the
platform.cozystack.io/no-delete=true label.
...
validations:
- expression: "false"
...
validationActions: [Deny]
$ git log --format=%H -1 --diff-filter=A -- packages/core/platform/templates/deletion-protection.yaml
0c17d1870cef5a51a891928730d470ea556b1c8d
$ git tag --contains 0c17d1870 | sort -V | head -1
v1.5.0The tenant-root Namespace carries that label and no keep (the keep a few lines below is on the sibling HelmRelease), which is exactly why the v1.5, v1.6 and next copies correctly drop it from the list one sentence after a rule saying it should go:
$ git show origin/main:packages/system/cozystack-basics/templates/tenant-root.yaml | head -8
kind: Namespace
metadata:
name: tenant-root
labels:
platform.cozystack.io/no-delete: "true"It matters past that one example, because the rule is what a reader applies to other components. Two more labelled-and-unkept objects belong to removable packages:
$ for f in packages/system/linstor/templates/cluster.yaml packages/system/cert-manager-issuers/templates/cluster-issuers.yaml; do
> echo "$f no-delete=$(git show origin/main:$f | grep -c no-delete) keep=$(git show origin/main:$f | grep -c resource-policy)"; done
packages/system/linstor/templates/cluster.yaml no-delete=1 keep=0
packages/system/cert-manager-issuers/templates/cluster-issuers.yaml no-delete=3 keep=0An operator removing cozystack.linstor or cozystack.cert-manager concludes from this page that those go with the release. What they get is an admission denial partway through the uninstall, a different end state with a different recovery. Naming the label next to the annotation, and pointing at the documented bypass (kubectl label <kind> <name> platform.cozystack.io/no-delete-), is enough. The v1.0 to v1.4 copies are accurate as written.
There was a problem hiding this comment.
Fixed in f621e84, on v1.5, v1.6 and next only, since v1.0 through v1.4 predate the policy. The Warning names the label next to the annotation, gives kubectl get <kind> --all-namespaces --selector platform.cozystack.io/no-delete=true for finding what carries it, and kubectl label <kind> <name> --namespace <ns> platform.cozystack.io/no-delete- for taking it off, with the consequence spelled out. cozystack.cozystack-basics labels the tenant-root Namespace and the tenant-root HelmRelease, the HelmRelease is kept by its annotation, and unlabelling the Namespace hands the root tenant to the uninstall. The label has to come off every labelled object in a release, which the cert-manager-issuers release of cozystack.cert-manager makes concrete with three ClusterIssuers.
Chasing your point further turned up a worse outcome than "the object stays". Uninstall.Run bails on any error out of deleteRelease (pkg/action/uninstall.go:122-125), and reconcileReleaseDeletion passes that error up, so the finalizer stays on for as long as the denial does. So removing cozystack.cozystack-basics on v1.5+ leaves the HelmRelease sitting in deletion and the kubectl wait on the page runs to its timeout. The Warning says that now, not just that the object survives.
| On v1.0 the platform does not annotate the Packages it renders with `helm.sh/resource-policy: keep`, so adding a name to `disabledPackages` also removes the component when it is already installed. The next platform reconcile drops the Package, the operator's ownerReference takes the component's HelmRelease with it, and Flux uninstalls the release. There is no second command and no confirmation step, so back up anything you still need before making that edit. | ||
|
|
||
| {{% alert title="Warning" color="warning" %}} | ||
| Uninstalling the component's Helm release destroys more than the workloads. Anything the chart rendered as an ordinary template without `helm.sh/resource-policy: keep` goes with the release, CRDs and namespaces included, and Kubernetes deletes every custom resource of those CRD kinds along with them. Removing `cozystack.metallb` takes every CRD the MetalLB chart bundles, subcharts included, and with them every custom resource of those kinds cluster-wide; removing `cozystack.cozystack-basics` takes the `cozy-public` and `tenant-root` namespaces and everything stored in them, which is every application in the root tenant. Back up anything you still need first. |
There was a problem hiding this comment.
[MAJOR] the v0 tree keeps the bare command and gets none of the warning
Out of this diff, and pre-existing, so a follow-up PR is a fine resolution. Raising it here because it is the same gap you folded v1.0 in to close last round, and v0 is the last tree still carrying the original line:
$ grep -n 'kubectl delete hr' content/en/docs/v0/operations/configuration/components.md
66:kubectl delete hr -n <namespace> <component>
$ grep -c '^content/en/docs/v0/' /tmp/...-files.txt
0The page is published and carries no deprecation banner:
$ sed -n '236,239p' hugo.yaml
- version: "v0"
url: "/docs/v0/"
id: "v0"
order: 1The destruction the Warning describes belongs to helm uninstall, not to Package objects, so it applies to v0 in full: at v0.41.11 the MetalLB chart renders its CRDs as ordinary templates with no resource-policy, and a v0 reader following line 66 loses every IPAddressPool, L2Advertisement and BGPPeer cluster-wide. "v0 predates Package objects" excuses the recipe, not the missing warning.
There was a problem hiding this comment.
Folded in here rather than deferred, at f621e84. v0 keeps its recipe and gets the warning ahead of the command. I checked charts/metallb/charts/crds/templates/crds.yaml at v0.1.0, v0.10.0, v0.20.0, v0.30.0 and v0.41.11: no resource-policy at any of them, so the destruction is the same on every 0.x line. The v0 copy drops the cozystack.cozystack-basics half of the sentence, that package does not exist before v1.
| Regular bundle components can, on the other hand, be disabled (excluded) from the installation, when you don't need them. | ||
|
|
||
| Use `bundles.enabledPackages` and `bundles.disabledPackages` in the Platform Package values. | ||
| Every entry in those lists is a fully-qualified Package name under the `cozystack.` prefix — run `kubectl get package` to see the exact names on your cluster. |
There was a problem hiding this comment.
[MINOR] kubectl get package cannot list a name that is not enabled yet
The sentence offers kubectl get package as the discovery step for both enabledPackages and disabledPackages, and it only answers for the second. An optional package has no Package object until its name is already in enabledPackages (packages/core/platform/templates/_helpers.tpl renders it under if and (has $name $enabled) (not (has $name $disabled))), and cozystack.hetzner-robotlb, the worked example right below, is one of those. PackageSources are rendered unconditionally from sources/*.yaml, so kubectl get packagesource answers for both lists.
The same wording is already on v1.2 through next unchanged, so fixing it is a sweep rather than a change to this hunk.
Separately on the v1.0 page: "Disabling components must be done before installing Cozystack." still sits directly above the new paragraph explaining that the same edit removes an already-installed component.
There was a problem hiding this comment.
Fixed in f621e84, swept across v1.1 through next rather than only the hunk. The discovery step is kubectl get packagesource now, with a clause saying kubectl get package answers only for disabledPackages. The and (has $name $enabled) (not (has $name $disabled)) gate is in _helpers.tpl from v1.1.0 on, and templates/sources.yaml globs sources/*.yaml unconditionally at all of those tags. The v1.0 line you noted at the end is gone as well: that page opens with "Disabling a component before installing Cozystack keeps it out of the installation entirely".
54cafa6 to
af929c0
Compare
Removing an installed component was documented as `kubectl delete hr -n <namespace> <component>`. The Package that emitted the release owns it through a controller ownerReference, and the operator watches HelmReleases with Owns(), so the delete fires the reconcile that recreates it. The command reports success, nothing logs an error, and the component is running again afterwards. That is not the harmless no-op it looks like. The operator sets no uninstall policy on the HelmReleases it builds, so Flux's default applies and an unsuspended release is uninstalled when the object goes away. Anything the uninstall destroys is gone before the reinstall brings the workloads back, and the restored release hides that it happened. The page now says so, for the benefit of anyone who already ran the old recipe. Deleting the cluster-scoped Package is what cascades into the uninstall deliberately. It needs the disabledPackages entry to land first, or the next platform upgrade renders the Package again and the removal undoes itself one level up. The reader confirms that by reading the disabledPackages list back off the platform HelmRelease, which is the only observation here that cannot go green early. A condition wait returns instantly against the Ready left standing by the previous reconcile; re-reading the Package after deleting it returns NotFound unconditionally; and comparing observedGeneration against generation on that HelmRelease is equal in exactly the window that matters, because the operator has not yet copied the edit across from the Package the reader changed. What the uninstall takes is stated above the command rather than after it, in a warning callout. Anything the chart rendered as an ordinary template and did not annotate resource-policy: keep goes with the release, CRDs and namespaces alike, and the apiserver cascades every custom resource of those CRD kinds. Removing cozystack.metallb takes the MetalLB CRDs and the objects of those kinds cluster-wide; removing cozystack.cozystack-basics takes the cozy-public namespace, which that chart renders as a plain template and which is not its install namespace. The one exception is the namespace a component installs into: the operator applies that itself, outside the release, so the uninstall never had it. The v1.1 and v1.2 pages qualify the step count in place, next to the instruction it governs, because the behaviour changed inside those lines rather than between them. The keep annotation on Packages arrived in v1.1.5 and v1.2.1 as backports, so v1.1.0 through v1.1.4 and v1.2.0 have no annotation and adding the name to disabledPackages removes the component by itself, putting the destruction on that step instead of the delete. The clauses name their range rather than saying "and earlier", which would be false across lines: v1.1.5 through v1.1.7 are lower-versioned than v1.2.0 and do carry the annotation. The same pages offered `kubectl get package` as the way to find the names for both lists, and it answers for one. An optional package is rendered under `and (has $name $enabled) (not (has $name $disabled))`, so it has no Package object until its name is already in enabledPackages. PackageSources are rendered unconditionally from sources/*.yaml, so `kubectl get packagesource` answers for both. Applied to next and v1.1 through v1.6. Left out v1.0, whose line never received the annotation at all, so that page needs the single-step correction rather than this one. v0 predates Package objects: its platform chart renders HelmReleases directly, so deleting the HelmRelease there names the object the chart creates. The v1.1 disabledPackages examples listed bare names, which match nothing against the fully-qualified names the platform chart feeds the helper. Both copies are fixed: the one in the components page, and the one in the Hetzner install guide that page links to as its worked example. Leaving the second would have had v1.1 teach the qualified form on one page and hand out the broken form on the page it cites, on the one provider where swapping MetalLB for RobotLB is mandatory. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la>
e3ad79e to
185073e
Compare
The named CRD kinds in the warning cover the top-level MetalLB chart only; a subchart such as frr-k8s brings its own group, so the list undercounts what an uninstall takes. Point at the chart instead of enumerating kinds, which also stops the list needing maintenance per component. Package carries no finalizer, so deleting it returns before the Helm uninstall that follows it has done anything. Add the wait that tells an operator the destructive part is over. That wait needs a HelmRelease name and a namespace, and neither follows from the Package name: both come from the PackageSource install block, and one Package can render several releases. The operator labels every HelmRelease it builds with the Package that owns it, so listing by that label before the delete produces the values and enumerates the releases. The same listing prints spec.suspend, because helm-controller skips the uninstall for a suspended HelmRelease and only drops its finalizer, which would leave the component's objects behind with nothing managing them. `kubectl wait --for=delete` also exits 0 for a name that never existed, so the page says to take the values from the listing rather than guess them. Through v1.4 the cozystack-basics chart also renders the tenant-root namespace as a plain template with no keep annotation, so uninstalling it takes the whole root tenant, not just cozy-public. From v1.5 that namespace carries the no-delete label the platform's admission policy enforces, so the later pages keep the shorter example. The annotation is not the whole survival rule on those pages either: the policy denies DELETE on any labelled object, so the v1.5, v1.6 and next warnings name the label beside the annotation and give the way to drop it. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la>
v1.0 documented `kubectl delete hr` as the way to remove an installed component, with no warning about what an uninstall takes with it. That command destroys the CRDs and every custom resource of those kinds, then the Package recreates the HelmRelease and the chart reinstalls, so the workloads come back and the data does not. v1.0 needs a different correction from later versions rather than a copy of it: the platform does not annotate rendered Packages with `helm.sh/resource-policy: keep`, so `disabledPackages` alone already removes an installed component there, in one step and with no confirmation. Because the removal starts when the operator picks that edit up, the completion signal has to be set up before the edit: the label selector that finds the component's HelmReleases needs the Package name, and the Package goes away with the component. The page puts the listing ahead of the edit for that reason, and keeps the same suspend check and delete wait the later versions use. Also qualify the package names in the Hetzner and components examples. `disabledPackages` and `enabledPackages` are matched against the full `cozystack.<name>` form, so the bare names did nothing. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la>
The v0 removal recipe deletes the component's Helm release and says nothing about what the uninstall takes with it. The MetalLB chart renders its CRDs as ordinary subchart templates with no helm.sh/resource-policy annotation on every 0.x line, so the uninstall drops them and the apiserver cascades every custom resource of those kinds cluster-wide. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la>
185073e to
f621e84
Compare
|
IvanHunters All five are fixed in f621e84. v0 went in here instead of a follow-up, it was two lines. v1.0, v1.1 and v1.2 have the listing and the wait now. On the single-step paths the page says to take the listing before the values edit, since the Package the selector needs goes away with the component. The PR body is rewritten. The i18n line already said it passes and it still does. On the condition wait I think we agree on the mechanism and I stated mine too loosely: at the point that sentence is about, the reader has just edited the Package and the operator has not copied the values into the HelmRelease yet, so |
kubectl delete hr -n <namespace> <component>is documented as the way to remove an installed component. It removes nothing, and it is not harmless. The Package that produced the release owns the HelmRelease through a controller ownerReference, and the operator watches them withOwns(&helmv2.HelmRelease{}), so the delete fires the reconcile that recreates it.buildHelmReleaseSpecsets no uninstall policy, so Flux's default applies to an unsuspended HelmRelease and the release is uninstalled on the way out. The workloads come back with the reinstall. Anything the uninstall destroyed does not, and the restored release hides that it happened.Deleting the Package is what cascades into the uninstall deliberately. The
disabledPackagesentry has to land first, or the next platform upgrade renders the Package again and the removal undoes itself one level up. The page checks that by reading the list back from thecozystack-platformHelmRelease, since the operator copies the Platform Package's values into that HelmRelease'sspec.values. The obvious alternatives go green before the edit has landed. A condition wait on that HelmRelease passes while the operator has not copied the values across, becausemetadata.generationhas not moved and--for=condition=only blocks whileobservedGenerationis behind it. Re-reading the Package after deleting it returns NotFound either way, and nothing holds a finalizer on the Package, so the delete returns before the uninstall runs.The completion signal is a
kubectl wait --for=deleteon the HelmRelease, and that needs a name and a namespace the Package name does not give: both come from the PackageSourceinstallblock. The operator labels every HelmRelease it renders with the Package that owns it, so akubectl get helmrelease --all-namespaces --selector cozystack.io/package=<name>before the delete produces both values and covers a Package that renders several releases. The same listing printsspec.suspend, because helm-controller skips the uninstall for a suspended HelmRelease and only drops its finalizer, which would leave everything the release installed behind with nothing managing it. The page says that, and says that--for=deleteexits 0 for a name that was never there.What the uninstall takes is stated above the command, since making the recipe work is what arms it.
packages/system/metallb/charts/metallb/charts/crds/templates/crds.yamlrenders its CRDs as ordinary subchart templates with noresource-policyannotation, so helm uninstall deletes them and the apiserver cascades every custom resource of those kinds with them, IPAddressPool, L2Advertisement and BGPPeer among them. From v1.5 the frr-k8s subchart adds its own CRDs under another group, so the warning points at the chart and its subcharts rather than listing kinds.cert-manager-crdssetscrds.keepand its CRDs survive the same uninstall. Through v1.4,cozystack-basicsrenders thetenant-rootNamespace the same way, so the warning on those pages says its removal takes the root tenant. From v1.5 that namespace carries theplatform.cozystack.io/no-deletelabel instead, which a ValidatingAdmissionPolicy turns into a denied DELETE. That is a survival mechanism independent of the annotation, so the v1.5, v1.6 and next warnings name the label and how to drop it.v1.0 gets different text, not a copy. That line never got the
helm.sh/resource-policy: keepannotation on rendered Packages (none inpackages/core/platform/templates/_helpers.tplatv1.0.8), so adding a name todisabledPackagesalready removes an installed component there, in one step and with no confirmation. The page says that, carries the same warning, and drops thekubectl delete hrrecipe. Its listing has to be taken before the edit, since the Package the selector needs goes away with the component.keepreached v1.1 and v1.2 mid-line, in v1.1.5 and v1.2.1. Those two pages state the single-step behaviour of v1.1.0 to v1.1.4 and v1.2.0 in its own paragraph ahead of the two-step recipe.v0 keeps its recipe, which predates Package objects and works there, and now carries the warning: the MetalLB chart renders its CRDs without
resource-policyon every 0.x line as well.The v1.0 and v1.1
disabledPackagesexamples, on the components page and in the Hetzner guide, listed baremetallb/hetzner-robotlb. Those match nothing against the fully-qualified names the chart feeds the helper, so they're qualified now. The same pages pointed atkubectl get packageas the way to find those names; an optional package has no Package object until it is already inenabledPackages, so they point atkubectl get packagesourceinstead.Not taken:
cozypkg delresolves dependents and deletes in reverse topological order, which rawkubectl deletedoes not, but whether that subcommand exists varies across the version trees.hugo --gc --minifybuilds clean andhack/check-i18n.sh checkpasses.